package io.shuttle.mbe.core

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.net.Uri
import android.net.http.SslError
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.Message
import android.view.KeyEvent
import android.webkit.ClientCertRequest
import android.webkit.HttpAuthHandler
import android.webkit.RenderProcessGoneDetail
import android.webkit.SafeBrowsingResponse
import android.webkit.SslErrorHandler
import android.webkit.ValueCallback
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.webkit.WebMessageCompat
import androidx.webkit.WebViewAssetLoader
import androidx.webkit.WebViewFeature
import java.lang.ref.WeakReference

/**
 * TODO: handle multiple WebView instance
 */
open class Wab(
    var wv: WeakReference<WebView>,
    val preferWebChannel: Boolean = true,
    val preferWebListener: Boolean = true
) {
    private val abilityMap: MutableMap<String, Ability> = mutableMapOf()
    private var legacyWabChannel: LegacyWabChannel = LegacyWabChannel(wv, abilityMap)
    private var webListenerWabChannel: WebListenerWabChannel? = null
    private var webChannelWabChannel: WebChannelWabChannel? = null
    private val injectedScripts: MutableList<String> = mutableListOf()

    // TODO: heavy task should be handled by other thread.
    val thread = HandlerThread("Wab").also {
        it.start()
    }
    private val taskHandler = object : Handler(thread.looper) {
        override fun handleMessage(msg: Message) {
            // run in UI thread.
            wv.get()?.postDelayed({

            }, 1000)
            super.handleMessage(msg)
        }
    }

    companion object {
        const val LINK_LEGACY = "__Wab_Legacy__"
        const val LINK_LISTENER = "__Wab_Listener__"
        private var sInstance: Wab? = null
//        val instance: Wab
//            get() {
//                if (sInstance == null) {
//                    sInstance = Wab(wv)
//                }
//                return sInstance!!
//            }
    }

    init {

        if (preferWebListener) {
            WabCompat.checkRuntime(Build.VERSION_CODES.LOLLIPOP_MR1, onProcess = {
                WabCompat.checkFeatures(
                    setOf(
                        WebViewFeature.WEB_MESSAGE_LISTENER,
                        WebViewFeature.POST_WEB_MESSAGE
                    ), onProcess = {
                        webListenerWabChannel =
                            WebListenerWabChannel(wv, abilityMap, legacyWabChannel)
                    })
            })

        }
        if (preferWebChannel) {
            WabCompat.checkRuntime(Build.VERSION_CODES.M, onProcess = {
                WabCompat.checkFeatures(
                    setOf(
                        WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
                        WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK,
                        WebViewFeature.WEB_MESSAGE_PORT_CLOSE,
                        WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE,
                        WebViewFeature.POST_WEB_MESSAGE,
                    ),
                    onProcess = {
                        webChannelWabChannel =
                            WebChannelWabChannel(wv, abilityMap, legacyWabChannel)
                    })
            })
        }
        start()


        addAbility(
            "platform",
            Ability.Function { message, postMessageBody, sourceOrigin, isMainFrame, reply ->

            })

        addAbility(
            "runAsync",
            Ability.Function { message, postMessageBody, sourceOrigin, isMainFrame, reply ->
                val delay = postMessageBody?.args?.get("delay") as? Long ?: 3000
                val body = postMessageBody?.args?.get("body") as? String ?: ""
                if (body.isBlank()) {
                    return@Function
                }
                taskHandler.postDelayed({
                    wv.get()?.evaluateJavascript(body) {
                        // TODO: maybe reply?
                        Logger.i("[Wab]", "runAsync: $it")
                    }
                }, delay)
            }
        )

        addAbility(
            "isSupportWebListener",
            Ability.Value(JSObject().apply {
                put(
                    "webMessageListener",
                    WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)
                )
                put(
                    "postWebMessage",
                    WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)
                )
            })
        )
        addAbility(
            "isSupportWebChannel",
            Ability.Value(JSObject().apply {
                put(
                    "createWebMessageChannel",
                    WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)
                )
                put(
                    "webMessagePortSetMessageCallback",
                    WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK)
                )
                put(
                    "webMessagePortClose",
                    WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_CLOSE)
                )
                put(
                    "webMessagePostMessage",
                    WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE)
                )
                put(
                    "postWebMessage",
                    WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)
                )
            })
        )
    }


    fun postMessage(
        message: WebMessageCompat,
        legacyReply: ValueCallback<String>? = null,
        targetOrigin: Uri? = "*".toUri()
    ) {
        selectChannel().invokeJs(message, legacyReply, targetOrigin)
    }

    fun postMessage(
        message: String,
        legacyReply: ValueCallback<String>? = null,
        targetOrigin: Uri? = "*".toUri()
    ) {
        selectChannel().invokeJs(WebMessageCompat(message), legacyReply, targetOrigin)
    }

    fun addAbility(name: String, handler: Ability) {
        abilityMap[name] = handler
    }

    fun removeAbility(name: String, handler: Ability?) {
        if (handler != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                abilityMap.remove(name, handler)
                return
            }
        }
        abilityMap.remove(name)
    }


    fun destroy() {
        legacyWabChannel.onDestroy()
        webListenerWabChannel?.onDestroy()
        webChannelWabChannel?.onDestroy()

        abilityMap.clear()

        thread.quitSafely()

        wv.clear()
    }

    /**
     * Returns the working WabChannel based on the preferences set.
     */
    private fun selectChannel(): WabChannel {
        if (wabSupportType == WabSupportType.Channel) {
            Logger.i("[Wab]", "workingChannel: prefer WebChannel!")
            return webChannelWabChannel!!
        }
        if (wabSupportType == WabSupportType.Listener) {
            Logger.i("[Wab]", "workingChannel: prefer WebListener!")
            return webListenerWabChannel!!
        }
        Logger.i("[Wab]", "workingChannel: legacy channel!")
        return legacyWabChannel
    }

    val wabSupportType: WabSupportType
        get() {
            if (preferWebChannel && WabCompat.isSupportWebChannel && webChannelWabChannel != null) {
                return WabSupportType.Channel
            }
            if (preferWebListener && WabCompat.isSupportWebListener && webListenerWabChannel != null) {
                return WabSupportType.Listener
            }
            return WabSupportType.Legacy
        }

    fun start() {
        legacyWabChannel.onStart()
        webListenerWabChannel?.onStart()

        @SuppressLint("NewApi")
        val wvClient = wv.get()?.webViewClient
        wv.get()?.apply {
            settings.apply {
                allowFileAccess = true
                allowFileAccessFromFileURLs = true
                allowUniversalAccessFromFileURLs = true
                javaScriptEnabled = true
                javaScriptCanOpenWindowsAutomatically = true
                lightTouchEnabled = true
            }
        }
        wv.get()?.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(
                view: WebView?,
                request: WebResourceRequest?
            ): Boolean {
                return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    wvClient?.shouldOverrideUrlLoading(view, request)
                        ?: super.shouldOverrideUrlLoading(view, request)
                } else {
                    super.shouldOverrideUrlLoading(view, request)
                }
            }

            override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
                return wvClient?.shouldOverrideUrlLoading(view, url)
                    ?: super.shouldOverrideUrlLoading(view, url)
            }

            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                Logger.i("[Wab]", "onPageStarted: $url")
                addJsWrapper()
                wvClient?.onPageStarted(view, url, favicon)
                super.onPageStarted(view, url, favicon)
            }

            override fun onPageFinished(view: WebView?, url: String?) {
                wvClient?.onPageFinished(view, url)
                super.onPageFinished(view, url)
                webChannelWabChannel?.onStart()
            }

            override fun onLoadResource(view: WebView?, url: String?) {
                wvClient?.onLoadResource(view, url)
                super.onLoadResource(view, url)
            }

            override fun onPageCommitVisible(view: WebView?, url: String?) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    wvClient?.onPageCommitVisible(view, url)
                }
                super.onPageCommitVisible(view, url)
            }

            override fun shouldInterceptRequest(
                view: WebView?,
                url: String?
            ): WebResourceResponse? {
                return wvClient?.shouldInterceptRequest(view, url)
                    ?: super.shouldInterceptRequest(view, url)
            }

            override fun shouldInterceptRequest(
                view: WebView?,
                request: WebResourceRequest?
            ): WebResourceResponse? {
                return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    wvClient?.shouldInterceptRequest(view, request)
                        ?: super.shouldInterceptRequest(view, request)
                } else {
                    super.shouldInterceptRequest(view, request)
                }
            }

            override fun onTooManyRedirects(
                view: WebView?,
                cancelMsg: Message?,
                continueMsg: Message?
            ) {
                wvClient?.onTooManyRedirects(view, cancelMsg, continueMsg)
                super.onTooManyRedirects(view, cancelMsg, continueMsg)
            }

            override fun onReceivedClientCertRequest(
                view: WebView?,
                request: ClientCertRequest?
            ) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    wvClient?.onReceivedClientCertRequest(view, request)
                }
                super.onReceivedClientCertRequest(view, request)
            }

            override fun onReceivedError(
                view: WebView?,
                request: WebResourceRequest?,
                error: WebResourceError?
            ) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    wvClient?.onReceivedError(view, request, error)
                }
                super.onReceivedError(view, request, error)
            }

            override fun onFormResubmission(
                view: WebView?,
                dontResend: Message?,
                resend: Message?
            ) {
                wvClient?.onFormResubmission(view, dontResend, resend)
                super.onFormResubmission(view, dontResend, resend)
            }

            override fun doUpdateVisitedHistory(
                view: WebView?,
                url: String?,
                isReload: Boolean
            ) {
                wvClient?.doUpdateVisitedHistory(view, url, isReload)
                super.doUpdateVisitedHistory(view, url, isReload)
            }

            override fun onReceivedSslError(
                view: WebView?,
                handler: SslErrorHandler?,
                error: SslError?
            ) {
                wvClient?.onReceivedSslError(view, handler, error)
                super.onReceivedSslError(view, handler, error)
            }

            override fun onReceivedError(
                view: WebView?,
                errorCode: Int,
                description: String?,
                failingUrl: String?
            ) {
                wvClient?.onReceivedError(view, errorCode, description, failingUrl)
                super.onReceivedError(view, errorCode, description, failingUrl)
            }

            override fun onReceivedHttpAuthRequest(
                view: WebView?,
                handler: HttpAuthHandler?,
                host: String?,
                realm: String?
            ) {
                wvClient?.onReceivedHttpAuthRequest(view, handler, host, realm)
                super.onReceivedHttpAuthRequest(view, handler, host, realm)
            }

            override fun onReceivedHttpError(
                view: WebView?,
                request: WebResourceRequest?,
                errorResponse: WebResourceResponse?
            ) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    wvClient?.onReceivedHttpError(view, request, errorResponse)
                }
                super.onReceivedHttpError(view, request, errorResponse)
            }

            override fun onReceivedLoginRequest(
                view: WebView?,
                realm: String?,
                account: String?,
                args: String?
            ) {
                wvClient?.onReceivedLoginRequest(view, realm, account, args)
                super.onReceivedLoginRequest(view, realm, account, args)
            }

            override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {
                return wvClient?.shouldOverrideKeyEvent(view, event)
                    ?: super.shouldOverrideKeyEvent(view, event)
            }

            override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {
                wvClient?.onUnhandledKeyEvent(view, event)
                super.onUnhandledKeyEvent(view, event)
            }

            override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {
                wvClient?.onScaleChanged(view, oldScale, newScale)
                super.onScaleChanged(view, oldScale, newScale)
            }

            override fun onRenderProcessGone(
                view: WebView?,
                detail: RenderProcessGoneDetail?
            ): Boolean {
                return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    wvClient?.onRenderProcessGone(view, detail) ?: super.onRenderProcessGone(
                        view,
                        detail
                    )
                } else {
                    super.onRenderProcessGone(view, detail)
                }
            }

            @RequiresApi(Build.VERSION_CODES.O_MR1)
            override fun onSafeBrowsingHit(
                view: WebView?,
                request: WebResourceRequest?,
                threatType: Int,
                callback: SafeBrowsingResponse?
            ) {
                wvClient?.onSafeBrowsingHit(view, request, threatType, callback)
                super.onSafeBrowsingHit(view, request, threatType, callback)
            }
        }
    }

    fun addJsWrapper() {
        // FIXME:
        val webViewAssetLoader = WebViewAssetLoader.Builder()
            .setHttpAllowed(true)
            .setDomain("*")
            .build()
        val wabJs =
            wv.get()?.context?.assets?.open("wab.js")?.bufferedReader()
                .use { it?.readText() }
        wabJs?.let {
            Logger.i("[Wab] start inject wab.js!")
            wv.get()?.evaluateJavascript(
                """
                try {
                    $it
                } catch(e) {
                    console.log('[wab.js]', e)
                    throw e;
                }
            """.trimIndent()
            ) { msg ->
                Logger.i("[Wab] inject wab.js done! $msg")
            }
        }
        wv.get()?.evaluateJavascript(injectedScripts.joinToString(";")) { msg ->
            Logger.i("[Wab] inject injectedScripts done! $msg")
        }
    }

    fun injectJs(script: String, executeNow: Boolean = false) {
        if (!executeNow) {
            injectedScripts.add(script)
        } else {
            wv.get()?.evaluateJavascript(script) { msg ->
                Logger.i("[Wab] inject script done! $msg")
            }
        }
    }

    fun removeJs(script: String): Boolean {
        return injectedScripts.remove(script)
    }
}